using System;
using System.Reflection;

using Xamarin.Forms;

namespace Outcoder.UI.Xaml
{
	/// <summary>
	/// This class is used to select a <seealso cref="View"/>
	/// for a viewmodel which is specified by the page's binding context.
	/// This class allows you to populate a Multipage 
	/// via a data-bound list of viewmodels.
	/// </summary>
	public class ViewLocatorPage : ContentPage, IViewLocator
    {
		public static readonly BindableProperty ViewLocatorProperty;

		static ViewLocatorPage()
		{
			ViewLocatorProperty = BindableProperty.Create("ViewLocator", 
				typeof(IViewLocator), typeof(ViewLocatorPage), 
				null, BindingMode.TwoWay);
		}

		/// <summary>
		/// Gets or Sets the <c>IViewLocator</c>  that is used to resolve 
		/// a view for the binding context of the page. 
		/// By default the current instance of the <c>ViewLocatorPage</c> is used.
		/// </summary>
		public IViewLocator ViewLocator
		{
			get
			{
				return (IViewLocator)GetValue(ViewLocatorProperty);
			}
			set
			{
				SetValue(ViewLocatorProperty, value);
			}
		}

		public ViewLocatorPage()
	    {	
			BindingContextChanged += HandleBindingContextChanged;
	    }
		
		void HandleBindingContextChanged(object sender, EventArgs e)
		{
			var context = BindingContext;
			
			if (context == null)
			{
				Content = null;
				return;
			}
			
			var parent = Parent;
			if (parent != null && parent.BindingContext == context)
			{
				/* The BindingContext may be set to the parent's context 
				 * before being set to a child viewmodel. */
				return;
			}

			IViewLocator locator = ViewLocator;
			if (locator == null)
			{
				ViewLocator = locator = this;
			}

			var newContent = locator.LocateView(BindingContext, Content);
			if (newContent != Content)
			{
				Content = newContent;
			}
		}

		View IViewLocator.LocateView(object bindingContext, View currentView)
		{
			if (bindingContext == null)
			{
				return null;
			}

			Type bindingContextType = bindingContext.GetType();
			string typeName = bindingContextType.FullName;

			const string viewModelSuffix = "Model";
			if (typeName.EndsWith(viewModelSuffix))
			{
				string viewName = typeName.Substring(0, typeName.Length - viewModelSuffix.Length);

				var currentContent = currentView;
				if (currentContent != null && currentContent.GetType().FullName == viewName)
				{
					return currentView;
				}

				var assembly = bindingContextType.Assembly;
				Type type = Type.GetType(viewName + ", " + assembly.FullName, false);

				if (type != null)
				{
					var content = Dependency.Resolve(type);
					View view = (View)content;
					view.BindingContext = bindingContext;
					view.BindToInfrastructure();
					return view;
				}
			}

			return null;
		}
    }

	/// <summary>
	/// Resolves a View object to present a viewmodel within a Multipage.
	/// </summary>
	public interface IViewLocator
	{
		/// <summary>
		/// Locates a view based on the binding context.
		/// </summary>
		/// <param name="bindingContext">The binding context of the view.</param>
		/// <param name="currentView">The existing view which may be used to determine 
		/// if no new view needs to be created.</param>
		/// <returns>A view to be placed in Multipage or <c>null</c> 
		/// if no applicable view can be located.</returns>
		View LocateView(object bindingContext, View currentView);
	}
}
